package airportCoordination;

import org.json.JSONObject;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.ResultSet;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Sender {

	private static int current_id;
	
	private static List<PacketLog> logbox;
	private static Lock lock;
	
	private static String SQL_CONNECTION = "jdbc:mysql://localhost/snevTracker";
	private static String SQL_USER = "snev";
	private static String SQL_PASS = "snevpw";
	
	private static final String AIRPORT_IPS_PORTS = "SELECT ip, dimPort FROM Airports WHERE NOT ip = '%s';";
	private static final String AIRPORT_DETAILS = "SELECT ip, dimPort, evPort, esPort FROM Airports WHERE ip = '%s';";
	private static final String AIRPORTS_DETAILS = "SELECT ip, dimPort, evPort, esPort FROM Airports WHERE NOT ip = '%s';";
	private static final String AIRPORT_FLIGHTS = "SELECT flight FROM Flights WHERE ip = '%s';";
	
	public static void randomize()
	{
		current_id = (new java.util.Random()).nextInt();
	}
	
	public static void set_logbox(List<PacketLog> lb, Lock l)
	{
		logbox = lb;
		lock = l;
	}
	
	public static void set_sqlconfig(String sql_connection, String sql_user, String sql_pass)
	{
		SQL_CONNECTION = sql_connection;
		SQL_USER = sql_user;
		SQL_PASS = sql_pass;
	}
	
	public static List<PacketLog> get_logbox()
	{
		return logbox;
	}	
	
	static void Sync(String ip, boolean send_all_to_origin)
	{
		int id = ++current_id;
		String message;
		int port = 0;
		
		JSONObject msg;
		ResultSet details;
		ResultSet flights;
		Statement st;

		try{
			st = DriverManager.getConnection(SQL_CONNECTION, SQL_USER, SQL_PASS).createStatement();
		}catch(Exception e){ System.out.print("Error connecting with database: " + e.getMessage() + "\n"); return; }
		
		try{
			details = st.executeQuery(String.format(AIRPORT_DETAILS, ip));
			
			if (!details.first())
			{
				System.out.print("Expected data not found, possible database corruption.\n");
				return;
			}
		
			msg = new JSONObject();
			msg.put("type", "SYNC");
			msg.put("id", id);
			msg.put("ip", ip);
			msg.put("dimPort", details.getInt("dimPort"));
			msg.put("evPort", details.getInt("evPort"));
			msg.put("esPort", details.getInt("esPort"));
			
			port = details.getInt("dimPort");

			flights = st.executeQuery(String.format(AIRPORT_FLIGHTS, ip));

			flights.last();
			msg.put("nFlights", flights.getRow());
			boolean valid = flights.first();
			while (valid)
			{
				msg.put("fp"+String.valueOf(flights.getRow()-1), flights.getInt("flight"));
				valid = flights.next();
			}
			
			message = msg.toString();
		}catch(Exception e){ System.out.print("Error generating package: " + e.getMessage() + "\n"); return; }
		
		send_to_all(ip, id, message);
		send_all_to(ip, port);
	}
	
	static void Unplug(String ip)
	{
		int id = ++current_id;
		String message;
		
		try{
			JSONObject msg;

			msg = new JSONObject();
			msg.put("type", "S_UNPLUG");
			msg.put("id", id);
			msg.put("ip", ip);
			message = msg.toString();
		}
		catch(Exception e)
		{
			System.out.print("Error generating packet\n");
			return;
		}
		
		send_to_all(ip, id, message);
	}
	
	static void error(String ip, int port, int id)
	{
		String message;
		
		System.out.print("Sending error to " + ip + ":" + String.valueOf(port) + " with id:" + String.valueOf(id) + "\n");

		try{
			JSONObject msg;

			msg = new JSONObject();
			msg.put("type", "ERROR");
			msg.put("id", id);
			message = msg.toString();
		}
		catch(Exception e)
		{
			System.out.print("Error generating packet\n");
			return;
		}
		
		send_packet(ip, port, message);
	}
	
	static void ok(String ip, int port, int id)
	{
		String message;
		
		System.out.print("Sending ok to " + ip + ":" + String.valueOf(port) + " with id:" + String.valueOf(id) + "\n");
		
		try{
			JSONObject msg;

			msg = new JSONObject();
			msg.put("type", "OK");
			msg.put("id", id);
			message = msg.toString();
		}
		catch(Exception e)
		{
			System.out.print("Error generating packet\n");
			return;
		}
		
		send_packet(ip, port, message);
	}

	static void ping(String ip, int port)
	{
		int id = ++current_id;
		String message;
		
		System.out.print("Sending ping to " + ip + ":" + String.valueOf(port) + " with id:" + String.valueOf(id) + "\n");

		try{
			JSONObject msg;

			msg = new JSONObject();
			msg.put("type", "PING");
			msg.put("id", id);
			message = msg.toString();
		}
		catch(Exception e)
		{
			System.out.print("Error generating packet\n");
			return;
		}
		
		// Pings have to be logged. Insertion must maintain order.
		lock.lock();
		int position = - Collections.binarySearch(logbox, new PacketLog(id, ip, port, message)) - 1;
		logbox.add(position, new PacketLog(id, ip, port, message, true));
		lock.unlock();
		
		send_packet(ip, port, message);		
	}
	
	private static void send_to_all(String ip, int id, String message)
	{
		ResultSet ips;		
		try{
			Statement sql = DriverManager.getConnection(SQL_CONNECTION, SQL_USER, SQL_PASS).createStatement();
			ips = sql.executeQuery(String.format(AIRPORT_IPS_PORTS, ip));
		}
		catch(Exception e)
		{
			System.out.print("Error getting info from database\n");
			return;
		}
		
		try{
			boolean valid = ips.first();
			while (valid)
			{
				if (ips.getString("ip").equals(ip))
				{
					valid = ips.next();
					continue;
				}
				
				// Log packets ** All messages through here must be logged and have an id.
				// Insertion must maintain order.
				lock.lock();
				int position = - Collections.binarySearch(logbox, new PacketLog(id, ips.getString("ip"), ips.getInt("dimPort"), message)) - 1;
				logbox.add(position, new PacketLog(id, ips.getString("ip"), ips.getInt("dimPort"), message));
				lock.unlock();
				
				send_packet(ips.getString("ip"), ips.getInt("dimPort"), message);
				
				valid = ips.next();
			}
		}
		catch(Exception e)
		{
			System.out.print("Error sending packet\n");
			if (lock.tryLock()) lock.unlock();
			return;
		}
	}

	public static void send_all_to(String ip, int port)
	{
		JSONObject msg;
		ResultSet airports;
		ResultSet flights;
		Statement st;
		Statement st2;
		
		try{
			st = DriverManager.getConnection(SQL_CONNECTION, SQL_USER, SQL_PASS).createStatement();
			st2 = DriverManager.getConnection(SQL_CONNECTION, SQL_USER, SQL_PASS).createStatement();
			airports = st.executeQuery(String.format(AIRPORTS_DETAILS, ip));
		}catch(Exception e){ System.out.print("Error getting data from database: " + e.getMessage() + "\n"); return; }
		
		try{
			if (airports.first() == false) return;
			for(airports.first(); !airports.isAfterLast(); airports.next())
			{
				int id = ++current_id;
				String message;

				msg = new JSONObject();
				msg.put("type", "SYNC");
				msg.put("id", id);
				msg.put("ip", airports.getString("ip"));
				msg.put("dimPort", airports.getInt("dimPort"));
				msg.put("evPort", airports.getInt("evPort"));
				msg.put("esPort", airports.getInt("esPort"));
				
				flights = st2.executeQuery(String.format(AIRPORT_FLIGHTS, airports.getObject("ip")));
				flights.last();
				msg.put("nFlights", flights.getRow());
				boolean valid = flights.first();
				
				while (valid)
				{
					msg.put("fp"+String.valueOf(flights.getRow()-1), flights.getInt("flight"));
					valid = flights.next();
				}
				
				message = msg.toString();
				
				// Log packets ** All messages through here must be logged and have an id.
				// Insertion must maintain order.
				lock.lock();
				int position = - Collections.binarySearch(logbox, new PacketLog(id, ip, port, message)) - 1;
				logbox.add(position, new PacketLog(id, ip, port, message));
				lock.unlock();

				send_packet(ip, port, message);
			}
		}
		catch(Exception e)
		{
			System.out.print("Error sending packages: "+ e.getMessage() + "\n");
			if (lock.tryLock()) lock.unlock();
			return;
		}
	}
	
	public static String toString(InetAddress ip)
	{
		byte[] b_ip = ip.getAddress();
		String s_ip = new String();
		for (int i=0; i < 4 ; i++)
		{
			int n_ip_i = b_ip[i];
			if (n_ip_i < 0) n_ip_i = 256+n_ip_i;
			s_ip += String.valueOf(n_ip_i) + ".";
		}
		return s_ip.substring(0, s_ip.length()-1);	
	}
	
	public static void send_packet(String ip, int port,String message)
	{
		try{
			DatagramSocket sock = new DatagramSocket();
			byte[] msg = message.getBytes();
			DatagramPacket packet = new DatagramPacket(msg, msg.length, InetAddress.getByName(ip), port);
			sock.send(packet);
		}catch(Exception e){ System.out.print("Error sending packet: " + e.getMessage() + "\n"); }
		System.out.print("Packet sent.\n");
	}
}
